#include "Dbkit.h"
#include "Cluster/Evaluate/evaluate.h"
#include "Cluster/Ckmeans.1d.dp/Ckmeans.1d.dp.h"
#include <QString>
#include <QStringList>
#include <QRegularExpression>
#include <QFile>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QDebug>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using std::cin;
using std::cout;
using std::endl;
using std::move;

#define q qDebug()
#define t(x) qDebug()<<#x":"<<(x)

Dbkit::Dbkit() {
    driver=new DriverSqlite();
    driver->setDatabase("company.db");
    mode="train";
}

Dbkit::~Dbkit() {
    disconnect();
    if(driver!=nullptr) delete driver;
}

bool Dbkit::execute(QString command) {
    QString commandName;
    QStringList commandArgumentList;
    interpretCommand(commandName,commandArgumentList,command);
    auto in_range=[](int x,int l,int r)->bool{return l<=x && x<=r;};
    if (commandName.isEmpty() || commandName[0] == '#' ) {
        return true;
    } else if(QString("open").contains(commandName)) {
        assert(commandArgumentList.size()==1);
        return connect(commandArgumentList[0]);
    } else if(QString("close").contains(commandName)) {
        return disconnect();
    } else if(QString("executescript").contains(commandName)) {
        assert(commandArgumentList.size()==1);
        return executeScript(commandArgumentList[0]);
    } else if (QString("key").contains(commandName)) {
        assert(commandArgumentList.size() == 1);
        return setKey(commandArgumentList[0]);
    } else if(QString("evaluate").contains(commandName)) {
        assert(in_range(commandArgumentList.size(),0,1));
        if(commandArgumentList.isEmpty()) return setEvaluation();
        else return setEvaluation(commandArgumentList[0]);
    } else if(QString("metastore").contains(commandName)) {
        assert(in_range(commandArgumentList.size(),0,1));
        if(commandArgumentList.isEmpty()) return setCheckpoint();
        else return setCheckpoint(commandArgumentList[0]);
    } else if(QString("copy").contains(commandName)) {
        assert(commandArgumentList.size()>=2);
        if(commandArgumentList.size()==2)
            return copy(commandArgumentList[0],commandArgumentList[1]);
        else
            return copy(commandArgumentList[0],commandArgumentList[1],
                        commandArgumentList.mid(2));
    } else if(QString("clone").contains(commandName)) {
        assert(commandArgumentList.size()==2);
        return clone(commandArgumentList[0],commandArgumentList[1]);
    } else if(QString("cluster").contains(commandName)) {
        assert(in_range(commandArgumentList.size(),2,3));
        if(commandArgumentList.size()==2)
            return cluster(commandArgumentList[0],commandArgumentList[1]);
        else
            return cluster(commandArgumentList[0],commandArgumentList[1],
                           commandArgumentList[2]);
    } else if(QString("rate").contains(commandName)) {
        assert(in_range(commandArgumentList.size(),2,4));
        if(commandArgumentList.size()==2)
            return rate(commandArgumentList[0],commandArgumentList[1]);
        else if(commandArgumentList.size()==3)
            return rate(commandArgumentList[0],commandArgumentList[1],
                        commandArgumentList[2]);
        else
            return rate(commandArgumentList[0],commandArgumentList[1],
                        commandArgumentList[2],commandArgumentList[3]);
    } else if(QString("count").contains(commandName)) {
        assert(commandArgumentList.size()==2);
        return count(commandArgumentList[0],commandArgumentList[1]);
    } else if(QString("save").contains(commandName)) {
        return save(commandArgumentList);
    } else if(QString("load").contains(commandName)) {
        return load(commandArgumentList);
    } else if(QString("drop").contains(commandName)) {
        return drop(commandArgumentList);
    } else if(QString("rename").contains(commandName)) {
        assert(commandArgumentList.size()==2);
        return rename(commandArgumentList[0],commandArgumentList[1]);
    }
    return true;
}

bool Dbkit::executeScript(QString script) {
    QFile file(script);
    if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
        return false;
    QStringList commandList;
    while(!file.atEnd()) {
        QString command=file.readLine().trimmed();
        if(command.isEmpty()) continue;
        commandList.append(command);
    }
    file.close();
    for(QString command:commandList)
        execute(command);
    return true;
}

bool Dbkit::connect(QString databaseName) {
    db.name=databaseName;
    driver->setDatabase(databaseName);
    driver->loadStructure(db);
    state = State::Connected;
    checkpointCache.clear();
    if(mode=="predict" && !pathCheckpoint.isEmpty()) {
        QFile file(pathCheckpoint);
        if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
            return false;
        while(!file.atEnd())
            checkpointCache.push_back(file.readLine().trimmed());
        file.close();
    }
    return true;
}

bool Dbkit::disconnect() {
    if (state == State::Disconnected) return true;
    if(mode=="train"
            && !checkpointCache.isEmpty()
            && !pathCheckpoint.isEmpty()) {
        QFile file(pathCheckpoint);
        if(!file.open(QIODevice::WriteOnly|QIODevice::Text))
            return false;
        file.write(checkpointCache.join('\n').toUtf8());
        file.close();
    }
    return true;
}

// Commands

bool Dbkit::setKey(QString key) {
    this->key = key;
    return true;
}

bool Dbkit::setMode(QString mode) {
    this->mode=mode;
	setCheckpoint(pathCheckpoint);
    return true;
}

bool Dbkit::setEvaluation(QString evaluation) {
    tableEvaluation=evaluation;
    return true;
}

bool Dbkit::setCheckpoint(QString checkpoint) {
    pathCheckpoint=checkpoint;
    checkpointCache.clear();
    if(mode=="predict" && !pathCheckpoint.isEmpty()) {
        QFile file(pathCheckpoint);
        if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
            return false;
        while(!file.atEnd())
            checkpointCache.push_back(file.readLine().trimmed());
        file.close();
    }
    return true;
}

bool Dbkit::copy(QString dst,QString src,QStringList columns) {
    if(!db.tableId.contains(src)) return false;
    Table &tableSrc=db.getTable(src);
    if(!db.tableId.contains(dst)) {
        Table newTable(dst);
        newTable.addColumn(move(tableSrc.columns[0]));
        db.addTable(move(newTable));
    }
    Table &tableDst=db.getTable(dst);
    if(columns.isEmpty()) columns=tableSrc.columnNames;
    for(QString column:columns)
        tableDst.addColumn(move(tableSrc.getColumn(column)));
    int nColumn=columns.size();
    QVector<int> iColumnSrc(nColumn),iColumnDst(nColumn);
    for(int i=0; i<nColumn; i++) {
        iColumnSrc[i]=tableSrc.columnId[columns[i]];
        iColumnDst[i]=tableDst.columnId[columns[i]];
    }
    QString id;
    for(Row &rowSrc:tableSrc.rows) {
        id=rowSrc[0].toString();
        if(!tableDst.rowId.contains(id)) tableDst.addId(id);
        Row &rowDst=tableDst.getRow(id);
        for(int i=0; i<nColumn; i++)
            rowDst[iColumnDst[i]]=rowSrc[iColumnSrc[i]];
    }
    return true;
}

const int MAXN=1e7,MAXK=2e1,MAXD=2e1;
bool Dbkit::cluster(QString dst, QString src, QString weight) {
	static std::vector<double> w(MAXD,1);
	static QVector<double> low(MAXD),high(MAXD);
	static QVector<bool> first(MAXD,true);
	static QVector<double> s(MAXD);
	QString lineLow,lineHigh,lineCenters;
	if(mode=="predict") {
		lineLow=checkpointCache.takeFirst().trimmed();
		lineHigh=checkpointCache.takeFirst().trimmed();
		lineCenters=checkpointCache.takeFirst().trimmed();
	}
	if(!db.tableId.contains(src)) return false;
	Table &tableDst=createTable(dst,key);
	QString target,type,arg,mode;
    QStringList tags;
    interpretName(target,type,arg,mode,src);
    QChar splitter(u'、');
    for(QChar ch:QString("、/")) if(arg.contains(ch)) {
            splitter=ch;
            break;
        }
    tags=arg.split(splitter);
    Table &tableSrc=db.getTable(src);
	int n=tableSrc.rows.size();
    if(n==0) return true;
    int d=tableSrc.rows[0].size()-1;
    if(d==0) return true;
    int k=tags.size();
	std::fill(w.begin(),w.end(),1);
	if(!weight.isEmpty() && db.tableId.contains(weight)) {
        Table &tableWeight=db.getTable(weight);
		for(Row &weightRow:tableWeight.rows)
			w[size_t(tableSrc.columnId[weightRow[1].toString()]-1)]=weightRow[2].toDouble();
    }
	int i,j;
	// Standardize
	if(mode=="predict") {
		QStringList values=lineLow.split(' ');
		for(int i=0; i<d; i++) low[i]=values[i].toDouble();
		values=lineHigh.split(' ');
		for(int i=0; i<d; i++) high[i]=values[i].toDouble();
	} else {
		double xj;
		first.fill(true);
		for(Row &row:tableSrc.rows) {
			for(j=0; j<d; j++) if(!row[j+1].isNull()) {
					xj=row[j+1].toDouble();
					if(first[j]) {
						low[j]=high[j]=xj;
						first[j]=false;
					} else {
						low[j]=std::min(low[j],xj);
						high[j]=std::max(high[j],xj);
					}
                }
		}
		lineLow.clear();
		lineHigh.clear();
		for(int i=0; i<d; i++) {
			lineLow.append(QString::asprintf("%.10f ",low[i]));
			lineHigh.append(QString::asprintf("%.10f ",high[i]));
		}
		checkpointCache.push_back(lineLow);
		checkpointCache.push_back(lineHigh);
	}
	for(i=0; i<d; i++) s[i]=high[i]-low[i];

    static double x[MAXN];
    double sumV,sumW;
    for(i=0; i<n; i++) {
        Row &row=tableSrc.rows[i];
        sumV=0;
        sumW=0;
        for(j=0; j<d; j++) if(!row[j+1].isNull()) {
                sumV+=s[j]==0.?w[size_t(j)]:(row[j+1].toDouble()-low[j])/s[j]*w[size_t(j)];
                sumW+=w[size_t(j)];
            }
        x[i]=sumW==0.?1:sumV/sumW;
    }
	static int y[MAXN];
    static double centers[MAXK],size[MAXK],withinss[MAXK],BICs[MAXK];
    tableDst.addColumn(Column(target,Column::Text));
    copyIds(tableDst,tableSrc);
    int iColumn=tableDst.columnId[target];
    if(mode=="predict") {
		QStringList values=lineCenters.split(' ');
        for(int i=0; i<k; i++) centers[i]=values[i].toDouble();
		for(int i=0; i<n; i++) {
            y[i]=0;
            for(int j=0; j<k; j++)
                if(std::abs(centers[j]-x[i])<std::abs(centers[y[i]]-x[i]))
                    y[i]=j;
        }
	} else {
        Ckmeans_1d_dp(x,n,k,k,nullptr,y,centers,size,withinss,BICs);
		lineCenters.clear();
        for(int i=0; i<k; i++)
			lineCenters.append(
                QString::asprintf("%.10f ",centers[i])
            );
		checkpointCache.push_back(lineCenters);
        if(!tableEvaluation.isEmpty()) {
            int nScore;
            double *score;
            char **method;
            evaluate(x,y,n,k,centers,size,&nScore,&score,&method);
            Table &tableEvaluation=createTable(this->tableEvaluation,"聚类目标");
            Row &rowEvaluation=tableEvaluation.addId(target);
            for(int i=0; i<nScore; i++) {
                tableEvaluation.addColumn(Column(method[i],Column::Real));
                rowEvaluation[i+1]=score[i];
            }
        }
    }
	for(int i=0; i<n; i++) {
        Row &rowSrc=tableSrc.rows[i];
        Row &rowDst=tableDst.getRow(rowSrc[0].toString());
        rowDst[iColumn]=tags[y[i]];
    }
	return true;
}

bool Dbkit::rate(QString dst,QString src,QString criteria,QString target) {
    if(!db.tableId.contains(src)) return false;
	Table &tableDst=createTable(dst,key);
	QString name,type,arg,mode;
    if(!interpretName(name,type,arg,mode,src)) return false;
    if(target.isEmpty()) target=arg;
    if(criteria.isEmpty()) criteria=name+"（标准："+target+"）";
    if(!db.tableId.contains(criteria)) return false;
    Table &tableSrc=db.getTable(src);
    if(tableSrc.rows.isEmpty()) return true;
    int n=tableSrc.rows.size();
    int d=tableSrc.rows[0].size()-1;
    Table &tableCriteria=db.getTable(criteria);
    QVector<QMap<QString,double>> score(d);
    for(Row &row:tableCriteria.rows)
		score[tableSrc.columnId[row[1].toString()]-1][row[2].toString()]
			=row[3].toDouble();
    tableDst.addColumn(Column(target,Column::Real));
    int iColumn=tableDst.columnId[target];
    copyIds(tableDst,tableSrc);
    double rating;
	for(int i=0; i<n; i++) {
        Row &row=tableSrc.rows[i];
        rating=0;
        for(int i=0; i<d; i++) if(!row[i+1].isNull())
                rating+=score[i][row[i+1].toString()];
        tableDst.getRow(row[0].toString())[iColumn]=rating;
    }
	return true;
}

bool Dbkit::count(QString dst,QString src) {
    QString target,type,arg,mode;
    if(!interpretName(target,type,arg,mode,src)) return false;
    if(!db.tableId.contains(src)) return false;
	Table &tableDst=createTable(dst,key);
	Table &tableSrc=db.getTable(src);
    if(tableSrc.rows.isEmpty()) return true;
    tableDst.addColumn(Column(target,Column::Integer));
    int iColumn=tableDst.columnId[target];
    copyIds(tableDst,tableSrc);
    int n=tableSrc.rows.size();
    int cnt;
    for(int i=0; i<n; i++) {
        Row &row=tableSrc.rows[i];
        cnt=-1;
        for(QVariant &value:row.values) if(!value.isNull()) cnt++;
        tableDst.getRow(row[0].toString())[iColumn]=cnt;
    }
    return true;
}

bool Dbkit::load(QStringList tables) {
    if(tables.isEmpty()) {
        driver->loadStructure(db);
        driver->loadDatabase(db);
    } else {
        for(QString &table:tables) {
            auto found=db.tableId.find(table);
            if(found==db.tableId.end()) continue;
            driver->loadTable(db.tables[*found]);
        }
    }
    return true;
}

bool Dbkit::save(QStringList tables) {
    if(tables.isEmpty()) {
        driver->saveDatabase(db);
    } else {
        for(QString &table:tables) {
            auto found=db.tableId.find(table);
            if(found==db.tableId.end()) continue;
            driver->saveTable(db.tables[*found]);
        }
    }
    return true;
}

bool Dbkit::drop(QStringList tables) {
    if(tables.isEmpty()) {
        db.clearTables();
    } else {
        for(QString &table:tables) {
            db.removeTable(table);
        }
    }
    return true;
}

bool Dbkit::rename(QString dst, QString src) {
    if(db.tableId.contains(dst)) return false;
    auto found=db.tableId.find(src);
    if(found==db.tableId.end()) return false;
    int iTable=*found;
    Table& table=db.tables[iTable];
    table.name=dst;
    db.tableId.erase(found);
    db.tableId[dst]=iTable;
    return true;
}

bool Dbkit::clone(QString dst, QString src) {
    db.removeTable(dst);
    db.addTable(move(db.getTable(src).clone(dst)));
    return true;
}

/* json_insertRows
 *	input{
 * 		code: <code> (0)
 * 		data: {
 * 			table: <table>
 * 			columns: [ <column>,... ]
 *  		rows: [ [ <value>,... ],... ]
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table,F2:no column,F3:row format,F4:conflicted)
 * 		data(F1): {}
 * 		data(F2): {
 * 			columns: [ <column>,... ]
 * 		}
 * 		data(F3): {
 * 			rows: [ <#row>,... ]
 * 		}
 * 		data(F4): {
 * 			rows: [ <#row>,... ]
 * 		}
 *  }
 */
bool Dbkit::json_insertRows(QString &output, QString input) {
    QString outputCode,inputCode;
    QJsonObject outputData,inputData;
    json_unpack(inputCode,inputData,input);
    QString tableName=inputData["table"].toString();
    auto found=db.tableId.find(tableName);
    if(found==db.tableId.end()) {
        outputCode="F1";
        json_pack(output,outputCode,outputData);
        return false;
    }
    Table &table=db.tables[*found];
    QStringList columnNames;
    QVector<int> iColumns;
    QJsonArray lackedColumnNames;
    for(QJsonValue value:inputData["columns"].toArray()) {
        QString columnName=value.toString();
        found=table.columnId.find(columnName);
        if(found!=table.columnId.end()) {
            columnNames.push_back(columnName);
            iColumns.push_back(*found);
        } else
            lackedColumnNames.push_back(columnName);
    }
    if(!lackedColumnNames.isEmpty()) {
        outputCode="F2";
        outputData["columns"]=lackedColumnNames;
        json_pack(output,outputCode,outputData);
        return false;
    }
    QJsonArray formaterrorRows;
    int iRow=-1;
    for(QJsonValue row:inputData["rows"].toArray()) {
        iRow++;
        if(row.toArray().size()!=columnNames.size()) formaterrorRows.push_back(iRow);
    }
    if(!formaterrorRows.isEmpty()) {
        outputCode="F3";
        outputData["columns"]=formaterrorRows;
        json_pack(output,outputCode,outputData);
        return false;
    }
    QJsonArray conflictingRows;
    iRow=-1;
    for(QJsonValue row:inputData["rows"].toArray()) {
        iRow++;
        if(table.rowId.contains(row.toArray()[0].toString()))
            conflictingRows.push_back(iRow);
    }
    if(!conflictingRows.isEmpty()) {
        outputCode="F4";
        outputData["rows"]=conflictingRows;
        json_pack(output,outputCode,outputData);
        return false;
    }
    for(QJsonValue value:inputData["rows"].toArray()) {
        QVariantList row=value.toArray().toVariantList();
        iRow++;
        Row &newRow=table.addId(row[0].toString());
        for(int i=1; i<row.size(); i++)
            newRow[iColumns[i]]=row[i];
    }
    outputCode="S0";
    json_pack(output,outputCode,outputData);
    return true;
}

/* json_deleteRows
 *	input{
 * 		code: <code> (0)
 * 		data: {
 * 			table: <table>
 * 			ids: [ <id>,... ]
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table,F2:no id)
 * 		data(F1): {}
 * 		data(F2): {
 * 			ids: [ <#id>,... ]
 * 		}
 *  }
 */
bool Dbkit::json_deleteRows(QString &output, QString input) {
    QString inputCode,outputCode;
    QJsonObject inputData,outputData;
    json_unpack(inputCode,inputData,input);
    QString tableName=inputData["table"].toString();
    auto found=db.tableId.find(tableName);
    if(found==db.tableId.end()) {
        outputCode="F1";
        json_pack(output,outputCode,outputData);
        return false;
    }
    Table &table=db.tables[*found];
    int iId=-1;
    QJsonArray lackedIds;
    for(QJsonValue value:inputData["ids"].toArray()) {
        iId++;
        if(!table.rowId.contains(value.toString()))
            lackedIds.push_back(iId);
    }
    if(!lackedIds.isEmpty()) {
        outputCode="F2";
        json_pack(output,outputCode,outputData);
        return false;
    }
    for(QJsonValue value:inputData["ids"].toArray())
        table.removeRow(value.toString());
    outputCode="S0";
    json_pack(output,outputCode,outputData);
    return true;
}

/* json_updateRows
 *	input{
 * 		code: <code> (0)
 * 		data: {
 * 			table: <table>
 * 			columns: [ <column>,... ]
 * 			rows: [ [ <value>,... ],... ]
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table,F2:no column,F3:row format,F4:no row)
 * 		data(F1): {}
 * 		data(F2): {
 * 			columns: [ <column>,... ]
 * 		}
 * 		data(F3): {
 * 			rows: [ <#row>,... ]
 * 		}
 * 		data(F4): {
 * 			rows: [ <#row>,... ]
 * 		}
 *  }
 */
bool Dbkit::json_updateRows(QString &output, QString input) {
    QString outputCode,inputCode;
    QJsonObject outputData,inputData;
    QJsonObject objectInput=QJsonDocument::fromJson(input.toUtf8()).object();
    json_unpack(inputCode,inputData,input);
    QString tableName=inputData["table"].toString();
    auto found=db.tableId.find(tableName);
    if(found==db.tableId.end()) {
        outputCode="F1";
        json_pack(output,outputCode,outputData);
        return false;
    }
    Table &table=db.tables[*found];
    QStringList columnNames;
    QVector<int> iColumns;
    QJsonArray lackedColumnNames;
    for(QJsonValue value:inputData["columns"].toArray()) {
        QString columnName=value.toString();
        found=table.columnId.find(columnName);
        if(found!=table.columnId.end()) {
            columnNames.push_back(columnName);
            iColumns.push_back(*found);
        } else
            lackedColumnNames.push_back(columnName);
    }
    if(!lackedColumnNames.isEmpty()) {
        outputCode="F2";
        outputData["columns"]=lackedColumnNames;
        json_pack(output,outputCode,outputData);
        return false;
    }
    QJsonArray formaterrorRows;
    int iRow=-1;
    for(QJsonValue row:inputData["rows"].toArray()) {
        iRow++;
        if(row.toArray().size()!=columnNames.size())
            formaterrorRows.push_back(iRow);
    }
    if(!formaterrorRows.isEmpty()) {
        outputCode="F3";
        outputData["columns"]=formaterrorRows;
        json_pack(output,outputCode,outputData);
        return false;
    }
    QJsonArray lackedRows;
    iRow=-1;
    for(QJsonValue row:inputData["rows"].toArray()) {
        iRow++;
        if(!table.rowId.contains(row.toArray()[0].toString()))
            lackedRows.push_back(iRow);
    }
    if(!lackedRows.isEmpty()) {
        outputCode="F4";
        outputData["rows"]=lackedRows;
        json_pack(output,outputCode,outputData);
        return false;
    }
    for(QJsonValue value:inputData["rows"].toArray()) {
        iRow++;
        QVariantList row=value.toArray().toVariantList();
        Row &existingRow=table.getRow(row[0].toString());
        for(int i=1; i<row.size(); i++)
            existingRow[iColumns[i]]=row[i];
    }
    outputCode="S0";
    json_pack(output,outputCode,outputData);
    return true;
}

/* json_selectRows
 *	input{
 * 		code: <code> (0)
 * 		data: {
 * 			table: <table>
 * 			columns: [ <column>,... ]
 * 			ids: [ <id>,... ]
 * 			constraints: {
 * 				<column>: [ <value>,... ]
 * 				...
 * 			}
 * 			orderby: [ <column>,... ]
 * 			ascending: [ <ascending>,... ]
 * 			limit: <limit>
 * 			page: <page>(1-based)
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table,F2:no column,F3:no id,F4:invalid page)
 * 		data(S0): {
 * 			current: <iPage>
 * 			total: <nPage>
 * 			rows: [ [ <value>,... ],... ]
 * 		}
 * 		data(F1): {}
 * 		data(F2): {
 * 			columns: [ <column>,... ]
 * 		}
 * 		data(F3): {
 * 			ids: [ <#id>,... ]
 * 		}
 * 		data(F4): {}
 *  }
 */
bool Dbkit::json_selectRows(QString &output, QString input) {
    QString inputCode,outputCode;
    QJsonObject inputData,outputData;
    json_unpack(inputCode,inputData,input);
    QString tableName=inputData["table"].toString();
    auto found=db.tableId.find(tableName);
    if(found==db.tableId.end()) {
        outputCode="F1";
        json_pack(output,outputCode,outputData);
        return false;
    }
    Table &table=db.tables[*found];
    QStringList columnNames;
    QVector<int> iColumns;
    QJsonArray lackedColumnNames;
    for(QJsonValue value:inputData["columns"].toArray()) {
        QString columnName=value.toString();
        found=table.columnId.find(columnName);
        if(found!=table.columnId.end()) {
            columnNames.push_back(columnName);
            iColumns.push_back(*found);
        } else
            lackedColumnNames.push_back(columnName);
    }
    QMap<int,QVariantList> constraints;
    QJsonObject constraintsObject=inputData["constraints"].toObject();
    for(auto it=constraintsObject.begin(); it!=constraintsObject.end(); it++) {
        QString columnName=it.key();
        found=table.columnId.find(columnName);
        if(found!=table.columnId.end())
            constraints.insert(*found,it.value().toArray().toVariantList());
        else
            lackedColumnNames.push_back(columnName);
    }
    QVector<int> orderby;
    for(QJsonValue value:inputData["orderby"].toArray()) {
        QString columnName=value.toString();
        found=table.columnId.find(columnName);
        if(found!=table.columnId.end())
            orderby.push_back(*found);
        else
            lackedColumnNames.push_back(columnName);
    }
    QVector<bool> ascending;
    for(QJsonValue value:inputData["ascending"].toArray())
        ascending.push_back(value.toBool());
    while(ascending.size()<orderby.size()) ascending.push_back(true);
    if(!lackedColumnNames.isEmpty()) {
        outputCode="F2";
        outputData["columns"]=lackedColumnNames;
        json_pack(output,outputCode,outputData);
        return false;
    }
    QStringList ids;
    QJsonArray lackedIds;
    int iId=-1;
    for(QJsonValue value:inputData["ids"].toArray()) {
        iId++;
        QString id=value.toString();
        if(!table.rowId.contains(id))
            lackedIds.push_back(iId);
        else
            ids.push_back(id);
    }
    if(!lackedIds.isEmpty()) {
        outputCode="F3";
        outputData["ids"]=lackedIds;
        json_pack(output,outputCode,outputData);
        return false;
    }
    auto ok=[&constraints](Row &row) {
        for(auto it=constraints.begin(); it!=constraints.end(); it++)
            if(!it.value().contains(row[it.key()])) return false;
        return true;
    };
    QVector<Row*> rows;
    if(!ids.isEmpty() && constraints.isEmpty()) {
        for(QString &id:ids) rows.push_back(&table.getRow(id));
    } else {
        if(!ids.isEmpty()) {
            for(const QString &id:ids) {
                Row &row=table.getRow(id);
                if(!ok(row)) continue;
                rows.push_back(&row);
            }
        } else {
            for(Row &row:table.rows) if(ok(row)) rows.push_back(&row);
        }
    }
    int limit=inputData["limit"].toInt();
    int page=inputData["page"].toInt();
    int l=limit*(page-1);
    int r=std::min(l+limit,rows.size());
    if(limit<1 || page<1 || l>=rows.size()) {
        outputCode="F4";
        json_pack(output,outputCode,outputData);
        return false;
    }
    int total=int(ceil(rows.size()*1.0/limit));
    outputData["total"]=total;
    int current=page;
    outputData["current"]=current;
    if(!orderby.isEmpty()) {
        auto cmp=[&orderby,&ascending](Row *l,Row *r)->bool{
            for(int i=0,j; i<orderby.size(); i++) {
                j=orderby[i];
                if(l->values[j]!=r->values[j])
                    return (l->values[j]<r->values[j])^(!ascending[i]);
            }
            return false;
        };
        std::sort(rows.begin(),rows.end(),cmp);
    }
    QJsonArray rowsArray;
    for(int i=l; i<r; i++) {
        QJsonArray rowArray;
        for(int j:iColumns)
            rowArray.push_back(rows[i]->values[j].toJsonValue());
        rowsArray.push_back(rowArray);
    }
    outputData["rows"]=rowsArray;
    outputCode="S0";
    json_pack(output,outputCode,outputData);
    return true;
}

/* json_selectJoinedRow
 *	input{
 * 		code: <code> (0)
 * 		data: {
 * 			tables: [ <table>,... ]
 * 			columns: [ [ <column>,... ],... ]
 * 			id: <id>
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table,F2:column format,F3:no column,F4:no id)
 * 		data(S0): {
 * 			row: [ <value>,... ]
 * 		}
 * 		data(F1): {}
 * 		data(F2): {
 * 			tables: [ <table>,... ]
 * 		}
 * 		data(F3): {
 * 			columns: [ [<column>,...],... ]
 * 		}
 * 		data(F4): {}
 *  }
 */
bool Dbkit::json_selectJoinedRow(QString &output, QString input) {
    QString inputCode,outputCode;
    QJsonObject inputData,outputData;
    json_unpack(inputCode,inputData,input);
    QJsonArray lackedTables;
    QVector<Table> tables;
    for(QJsonValue value:inputData["tables"].toArray()) {
        QString tableName=value.toString();
        auto found=db.tableId.find(tableName);
        if(found==db.tableId.end()) lackedTables.push_back(value);
        else tables.push_back(move(db.tables[*found]));
    }
    if(!lackedTables.isEmpty()) {
        outputCode="F1";
        outputData["tables"]=lackedTables;
        json_pack(output,outputCode,outputData);
        return false;
    }
    QJsonArray columnSets=inputData["columns"].toArray();
    if(columnSets.size()!=tables.size()) {
        outputCode="F2";
        json_pack(output,outputCode,outputData);
        return false;
    }
    QJsonArray lackedColumnSets;
    bool lackColumn=false;
    QVector<QVector<int>> iColumnSets;
    for(int i=0; i<tables.size(); i++) {
        Table &table=tables[i];
        QJsonArray columns=columnSets[i].toArray();
        QJsonArray lackedColumns;
        QVector<int> iColumns;
        for(QJsonValue column:columns) {
            QString columnName=column.toString();
            auto found=table.columnId.find(columnName);
            if(found==table.columnId.end()) lackedColumns.push_back(column);
            else iColumns.push_back(*found);
        }
        if(!lackedColumns.isEmpty()) lackColumn=true;
        lackedColumnSets.push_back(move(lackedColumns));
        iColumnSets.push_back(move(iColumns));
    }
    if(lackColumn) {
        outputCode="F3";
        outputData["columns"]=lackedColumnSets;
        json_pack(output,outputCode,outputData);
        return false;
    }
    QString id=inputData["id"].toString();
    QVariantList row;
    bool lackId=true;
    for(int i=0; i<tables.size(); i++) {
        Table &table=tables[i];
        QVector<int> &iColumns=iColumnSets[i];
        auto found=table.rowId.find(id);
        if(found==table.rowId.end()) {
            for(int j=0; j<iColumns.size(); j++)
                row.push_back(QVariant());
        } else {
            Row &fullRow=table.rows[*found];
            for(int j=0; j<iColumns.size(); j++)
                row.push_back(fullRow[iColumns[j]]);
            lackId=false;
        }
    }
    if(lackId) {
        outputCode="F4";
        json_pack(output,outputCode,outputData);
        return false;
    }
    outputData["row"]=QJsonArray::fromVariantList(row);
    outputCode="S0";
    json_pack(output,outputCode,outputData);
    return true;
}

/* json_getTables
 *	input{
 * 		code: <code> (0)
 * 		data: {}
 * 	}
 * 	output{
 * 		code: <code> (S0)
 * 		data(S0): {
 * 			tables: [ <table>,... ]
 * 		}
 * 	}
 */
bool Dbkit::json_getTables(QString &output, QString input) {
    QString inputCode,outputCode;
    QJsonObject inputData,outputData;
    json_unpack(inputCode,inputData,input);
    outputData["tables"]=QJsonArray::fromStringList(db.tableId.keys());
    outputCode="S0";
    json_pack(output,outputCode,outputData);
    return true;
}

/* json_getColumns
 *	input{
 * 		code: <code> (0)
 * 		data: {
 * 			table: <table>
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table)
 * 		data(S0): {
 * 			columns: [ <column>,... ]
 * 			types: [ <types>,... ]
 * 		}
 * 	}
 */
bool Dbkit::json_getColumns(QString &output, QString input) {
    QString inputCode,outputCode;
    QJsonObject inputData,outputData;
    json_unpack(inputCode,inputData,input);
    QString tableName=inputData["table"].toString();
    auto found=db.tableId.find(tableName);
    if(found==db.tableId.end()) {
        outputCode="F1";
        json_pack(output,outputCode,outputData);
        return false;
    }
    Table &table=db.tables[*found];
    outputData["columns"]=QJsonArray::fromStringList(table.columnNames);
    QJsonArray types;
    for(Column &column:table.columns)
        types.push_back(Column::TypeName[column.type]);
    outputData["types"]=types;
    outputCode="S0";
    json_pack(output,outputCode,outputData);
    return true;
}

/* json_count
 * 	input{
 * 		code: <code> (0)
 * 		data:{
 * 			table: <table>
 * 			column: <column>
 * 			[simple: <simple>]
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table,F2:no column)
 * 		data(S0):{
 * 			[count: { <value>:<count>,... }](simple!=True)
 * 			empty: <empty count>
 * 			valid: <valid count>
 * 			total: <total count>
 * 		}
 * 		data(F1):{}
 * 		data(F2):{}
 */
bool Dbkit::json_count(QString &output, QString input) {
    QString inputCode,outputCode;
    QJsonObject inputData,outputData;
    json_unpack(inputCode,inputData,input);
    QString tableName=inputData["table"].toString();
    auto found=db.tableId.find(tableName);
    if(found==db.tableId.end()) {
        outputCode="F1";
        json_pack(output,outputCode,outputData);
        return false;
    }
    Table &table=db.tables[*found];
    QString columnName=inputData["column"].toString();
    found=table.columnId.find(columnName);
    if(found==table.columnId.end()) {
        outputCode="F2";
        json_pack(output,outputCode,outputData);
        return false;
    }
    int iColumn=*found;
	if(!inputData.contains("simple")||inputData.value("simple").toBool()==false) {
		QMap<QVariant,int> count;
		for(Row &row:table.rows)
			count[row[iColumn]]++;
		QJsonObject object;
		for(QMap<QVariant,int>::iterator it=count.begin(); it!=count.end(); it++)
			object[it.key().toString()]=it.value();
		outputData["count"]=object;
	}
	int countEmpty=0;
	int countValid=0;
	for(Row &row:table.rows) {
		if(row[iColumn].isNull()) countEmpty++;
		else countValid++;
	}
	int countTotal=countEmpty+countValid;
	outputData["total"]=countTotal;
	outputData["empty"]=countEmpty;
	outputData["valid"]=countValid;
	outputCode="S0";
	json_pack(output,outputCode,outputData);
    return true;
}

/* json_sum
 * 	input{
 * 		code: <code> (0)
 * 		data:{
 * 			table: <table>
 * 			column: <column>
 * 		}
 * 	}
 * 	output{
 * 		code: <code> (S0,F1:no table,F2:no column)
 * 		data(S0):{
 * 			sum: <sum>
 * 		}
 * 		data(F1):{}
 * 		data(F2):{}
 */
bool Dbkit::json_sum(QString &output, QString input) {
    QString inputCode,outputCode;
    QJsonObject inputData,outputData;
    json_unpack(inputCode,inputData,input);
    QString tableName=inputData["table"].toString();
    auto found=db.tableId.find(tableName);
    if(found==db.tableId.end()) {
        outputCode="F1";
        json_pack(output,outputCode,outputData);
        return false;
    }
    Table &table=db.tables[*found];
    QString columnName=inputData["column"].toString();
    found=table.columnId.find(columnName);
    if(found==db.tableId.end()) {
        outputCode="F2";
        json_pack(output,outputCode,outputData);
        return false;
    }
    int iColumn=*found;
    double sum=0;
    for(Row &row:table.rows)
        sum+=row[iColumn].toDouble();
    outputCode="S0";
    outputData["sum"]=sum;
    json_pack(output,outputCode,outputData);
    return true;
}

void Dbkit::json_pack(QString &json, QString &code, QJsonObject &data) {
    QJsonObject pack;
    pack["code"]=code;
    pack["data"]=data;
    json=QJsonDocument(pack).toJson();
}

void Dbkit::json_unpack(QString &code, QJsonObject &data, QString &json) {
    QJsonObject pack=QJsonDocument::fromJson(json.toUtf8()).object();
    code=pack["code"].toString();
    data=pack["data"].toObject();
}

bool Dbkit::interpretName(
    QString &target, QString &type, QString &arg, QString &mode,
    QString name) {
    QRegularExpression re(
        "^(?<target>[^（）：_]*)"
        "(（(?<type>[^：）]*)(：(?<arg>[^）]*))?）)?"
        "(_(?<mode>predict|train))?$"
    );
    QRegularExpressionMatch match = re.match(name);
    if (!match.hasMatch()) return false;
    target=match.captured("target");
    type=match.captured("type");
    arg=match.captured("arg");
    mode=match.captured("mode");
    if(mode.isEmpty()) mode="train";
    return true;
}

bool Dbkit::interpretCommand(
    QString &commandName, QStringList &commandArgumentList,
    QString command
) {
    QString commandArguments;
    QStringList stringList;
    command=command.trimmed();
    if(command.isEmpty() || command[0]=='#') {
        commandName="";
        commandArgumentList.clear();
        return false;
    }
    stringList=command.split(':',QString::SkipEmptyParts);
    commandName = stringList[0].trimmed().toLower();
    if(stringList.size()==1)
        commandArguments = "";
    else
        commandArguments = stringList[1].trimmed();
    commandArgumentList=commandArguments.split(',',QString::SkipEmptyParts);
    for(int i=0; i<commandArgumentList.size(); i++)
        commandArgumentList[i]=commandArgumentList[i].trimmed();
    return true;
}

Table &Dbkit::createTable(QString tableName,QString keyName) {
    return db.addTable(Table(tableName,keyName));
}

void Dbkit::copyIds(Table &dst, Table &src) {
    for(QString &id:src.rowId.keys())
        dst.addId(id);
}

void Ckmeans_1d_dp(
    double* x, int n, int minK, int maxK, double* w,
    int* y, double* centers, double* size, double* withinss, double* BICs) {
    std::string estimate_k = "BIC";
    std::string method = "linear";
    kmeans_1d_dp(x, size_t(n), w, size_t(minK), size_t(maxK),
                 y, centers, withinss, size, BICs,
                 estimate_k, method, L2);
}

void evaluate(
    double* x, int* y, int n, int nCluster, double* centers, double* size,
    int* nScore, double** score, char*** method) {
    GetEvaluation(x, y, n, nCluster, centers, size, nScore, score, method);
}
